/*
 * Scheduler.java
 *
 * Created on May 24, 2007, 9:59 AM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package com.agentfactory.platform.scheduler;

import com.agentfactory.platform.core.Agent;
import com.agentfactory.platform.util.Logger;
import java.util.LinkedList;
import java.util.List;
import java.util.Observable;

/**
 * This is a singleton class that implements a global scheduler for an agent
 * platform.  Later versions of this system will be more closely associated
 * with a given agent platform.
 *
 * @author rem
 */
public class Scheduler extends Observable implements Runnable {
    /**
     * Set Class Debugging Level
     */
    private static final int CLASS_LOG_LEVEL = Logger.LEVEL_ERROR;
    
    private static final long DEFAULT_TIME_SLICE                    = 500;
    
    private static final int SUSPEND_PRIORITY                       = 4;
    private static final int RESUME_PRIORITY                        = 8; 
    private static final int STATE_INITIAL                          = 0;
    private static final int STATE_ACTIVE                           = 1;
    private static final int STATE_INACTIVE                         = 2;
    private static final int STATE_TERMINATE                        = 3;
    
    private static Scheduler scheduler;
    
    public static Scheduler getInstance() {
        if ( scheduler == null ) {
            Logger.detail("Starting Scheduler", CLASS_LOG_LEVEL);
            scheduler = new Scheduler();
            Thread thread = new Thread(scheduler, "Scheduler");
            scheduler.setThread(thread);
            thread.setPriority( Thread.MAX_PRIORITY );
            thread.start();
        }
        return scheduler;
    }
   
    private Thread thread;
    private List agents;
    private Agent active;
    private int state;
    
    /** Creates a new instance of Scheduler */
    private Scheduler() {
        agents = new LinkedList();
        state = STATE_INITIAL;
    }
    
    public synchronized void registerAgent( Agent agent ) {
        Logger.detail("Adding agent: " + agent.getName(), CLASS_LOG_LEVEL);
        agents.add( agent );
    }
    
    private synchronized void setActiveAgent() {
        if ( !agents.isEmpty() ) {
            if ( active != null ) {
                agents.add( active );
            }
        
            active = (Agent) agents.remove( 0 );
        }    
    }
    
    public void run() {
        boolean interrupted;
        while ( state != STATE_TERMINATE ) {
            interrupted = false;
            
            setActiveAgent();
            
            if ( active != null ) {
                if ( active.getState() == Agent.ACTIVE ) {
                    long startTime = System.currentTimeMillis();
		    Logger.detail("[Scheduler] about to execute: " + active.getName(),  CLASS_LOG_LEVEL);
                    active.execute();
                
                    try {
                        Thread.sleep( active.getTimeSlice() );
                    } catch ( InterruptedException ie ) {
                        interrupted = true;
                    }

                    if ( !interrupted ) {
                        active.endOfTimeSlice();
                        try {
                            synchronized ( this ) {
                                this.wait();
                            }
                        } catch ( InterruptedException ie) {
                        }
                    }
                    long duration = System.currentTimeMillis() - startTime;
                    setChanged();
                    notifyObservers("ACTIVITY " + active.getName() + " " + duration);
                } else if ( active.getState() == Agent.TERMINATING ) {
                    active.terminate();
                    active = null;
                }
            }
            
            // Yield control for a small portion of time to allow the
            // agent controller threads to move to a wait state
            // TODO (Rem Collier): Not sure if this is the best solution, but
            //                     it will do for now...
            try {
                Thread.sleep( 5 );
            } catch ( InterruptedException ie ) {
                // DO NOTHING
            }
        }
    }
    
    public void setThread(Thread thread) {
        this.thread = thread;
    }
    
    public void interrupt() {
        thread.interrupt();
    }
}
